package me.tonywang.quartz.service.impl;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.toolkit.IdWorker;
import me.tonywang.quartz.CronTaskFactoryBean;
import me.tonywang.quartz.QuartzUtils;
import me.tonywang.quartz.dao.CrontaskMapper;
import me.tonywang.quartz.entity.Crontask;
import me.tonywang.quartz.service.ICrontaskService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.MethodInvoker;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.util.List;

import static org.quartz.CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING;

/**
 * <p>
 * 定时任务信息表 服务实现类
 * </p>
 *
 * @author tony.wang
 * @since 2018-05-19
 */
@Service
public class CrontaskServiceImpl extends ServiceImpl<CrontaskMapper, Crontask> implements ICrontaskService, ApplicationContextAware {

    Logger logger = LoggerFactory.getLogger(getClass());

    public static String TEMP_GROUP = "TEMP";

    @Autowired
    Scheduler scheduler;

    @Autowired
    CrontaskMapper crontaskMapper;

    public void saveJob(Crontask task) throws ParseException, SchedulerException {

        checkJobConfig(task);

        String jobKey = task.getJobGroup().trim() + "." + task.getJobName().trim();
        addJobToQuartz(task);
        //添加成功后再插入表中
        task.setId(jobKey);
        insert(task);
    }

    public void updateJob(Crontask task) throws SchedulerException, ParseException {
        String id = task.getId();
        Assert.state(id != null, " id 未提供！");
        // 1 .jobName 和 group 不可修改
        boolean isGroupOrNameExists = task.getJobGroup() != null || task.getJobName() != null;
        if (isGroupOrNameExists) {
            String newJobKey = task.getJobGroup().trim() + "." + task.getJobName().trim();
            Assert.state(id.equals(newJobKey), "jobName 和 group 不可修改！");
        }
        // 2 . CronExpression 的校验
        boolean isCronExprExists = StringUtils.hasText(task.getCronExpression());
        if (isCronExprExists) {
            QuartzUtils.validateExpression(task.getCronExpression());
        }

        // 3. 校验是否method
        boolean isMethodExists = task.getTargetBeanName() != null || task.getTargetMethod() != null || task.getTargetMethod() != null;
        if (isMethodExists)
            prepareMethodInvoker(task);

        //4.信息填写无误
        super.updateById(task);

        //5.获取完整信息，重新添加到QUARTZ
        task = super.selectById(id);
        CronTaskFactoryBean cronTaskFactoryBean = getCronTaskFactoryBean(task);

        //手动执行的任务
        if (!StringUtils.hasText(task.getCronExpression())) {
            cronTaskFactoryBean.setDurability("1".equals(task.getDurability()));
            scheduler.addJob(cronTaskFactoryBean.getJobDetail(), true, true);
        } else {
            Trigger trigger = cronTaskFactoryBean.getCronTrigger();
            QuartzUtils.updateTriggerToScheduler(scheduler, trigger);
        }
    }


    @Override
    public void triggerJob(Crontask task) throws SchedulerException {
        QuartzUtils.triggerJob(scheduler, new JobKey(task.getJobName(), task.getJobGroup()));
    }

    @Override
    public void triggerTempJob(Crontask task) throws SchedulerException {
        triggerTempJob(task, 0);
    }


    @Override
    public void triggerTempJob(Crontask task, int startupDelay) throws SchedulerException {
        prepareMethodInvoker(task);
        //
        CronTaskFactoryBean cronTaskFactoryBean = new CronTaskFactoryBean();
        cronTaskFactoryBean.setName(task.getJobName() == null ? IdWorker.get32UUID() : task.getJobName());
        cronTaskFactoryBean.setGroup(TEMP_GROUP);
        cronTaskFactoryBean.setTargetBeanName(task.getTargetBeanName());
        cronTaskFactoryBean.setTargetMethod(task.getTargetMethod());
        cronTaskFactoryBean.setRequestObj(task.getRequestObj());
        if (startupDelay > 0)
            QuartzUtils.triggerTempJob(scheduler, cronTaskFactoryBean.getJobDetail(), startupDelay);
        else
            QuartzUtils.triggerTempJob(scheduler, cronTaskFactoryBean.getJobDetail());
    }

    @Override
    public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
        QuartzUtils.deleteJob(scheduler, new JobKey(jobName, jobGroup));
    }

    @Override
    public void deleteJobsInGroup(String groupName) throws SchedulerException {
        QuartzUtils.deleteJobs(scheduler, groupName);
    }

    @Override
    public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
        QuartzUtils.pauseJob(scheduler, new JobKey(jobName, jobGroup));
    }

    @Override
    public void pauseJobsInGroup(String groupName) throws SchedulerException {
        QuartzUtils.pauseJobs(scheduler, groupName);
    }

    @Override
    public void pauseAllJob() throws SchedulerException {
        QuartzUtils.pauseAll(scheduler);
    }

    @Override
    public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
        QuartzUtils.resumeJob(scheduler, new JobKey(jobName, jobGroup));
    }

    @Override
    public void resumeJobsInGroup(String jobGroup) throws SchedulerException {
        QuartzUtils.resumeJobs(scheduler, jobGroup);
    }

    @Override
    public void resumeAllJob() throws SchedulerException {
        QuartzUtils.resumeAll(scheduler);
    }

    @Override
    public SchedulerMetaData getMetaData() throws SchedulerException {
        return QuartzUtils.getMetaData(scheduler);
    }

    @Override
    public List<JobExecutionContext> getCurrentlyExecutingJobs() throws SchedulerException {
        return QuartzUtils.getCurrentlyExecutingJobs(scheduler);
    }

    private ApplicationContext ctx;

    private void prepareMethodInvoker(Crontask task) {
        MethodInvoker invoker = new MethodInvoker();
        Assert.state(ctx != null, "BeanFactory must be set when using 'targetBeanName'");
        invoker.setTargetClass(this.ctx.getType(task.getTargetBeanName()));
        invoker.setTargetObject(ctx.getBean(task.getTargetBeanName()));
        invoker.setTargetMethod(task.getTargetMethod());
        if (StringUtils.hasText(task.getRequestObj()))
            invoker.setArguments(task.getRequestObj());
        try {
            invoker.prepare();
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            throw new RuntimeException("NoSuchMethodException :" + e.getMessage());
        }
    }


    /**
     * 使用Crontask初始化CronTaskFactoryBean
     *
     * @param task
     * @return
     */
    private CronTaskFactoryBean getCronTaskFactoryBean(Crontask task) {
        CronTaskFactoryBean cronTaskFactoryBean = new CronTaskFactoryBean();
        cronTaskFactoryBean.setName(task.getJobName());
        cronTaskFactoryBean.setGroup(task.getJobGroup());
        cronTaskFactoryBean.setTargetBeanName(task.getTargetBeanName());
        cronTaskFactoryBean.setTargetMethod(task.getTargetMethod());
        cronTaskFactoryBean.setRequestObj(task.getRequestObj());
        cronTaskFactoryBean.setCronExpression(task.getCronExpression());
        cronTaskFactoryBean.setConcurrent("1".equals(task.getConcurrent()));
        cronTaskFactoryBean.setMisfireInstruction(task.getMisfirePolicy() == null ? MISFIRE_INSTRUCTION_DO_NOTHING : task.getMisfirePolicy());
        cronTaskFactoryBean.setPriority(task.getPriority() == null ? 0 : task.getPriority());
        return cronTaskFactoryBean;
    }

    /**
     * 使用Crontask初始化CronTaskFactoryBean
     *
     * @param task
     * @return
     */
    @Override
    public void checkJobConfig(Crontask task) throws ParseException {
        if (StringUtils.hasText(task.getCronExpression()))
            QuartzUtils.validateExpression(task.getCronExpression());
        prepareMethodInvoker(task);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;
    }


    /**
     * 初始化到QUARTZ
     *
     * @throws ParseException
     * @throws SchedulerException
     */
    @PostConstruct
    public void initCrontask() throws ParseException, SchedulerException {
        List<Crontask> crontasks = crontaskMapper.selectValidTask();
        for (Crontask task : crontasks) {
            checkJobConfig(task);
            //手动执行的任务
            addJobToQuartz(task);
        }
    }


    private void addJobToQuartz(Crontask task) throws SchedulerException {
        CronTaskFactoryBean cronTaskFactoryBean = getCronTaskFactoryBean(task);
        if (!StringUtils.hasText(task.getCronExpression())) {
            cronTaskFactoryBean.setDurability(true);
            scheduler.addJob(cronTaskFactoryBean.getJobDetail(), true, true);
        } else {
            Trigger trigger = cronTaskFactoryBean.getCronTrigger();
            QuartzUtils.addTriggerToScheduler(scheduler, trigger);
        }
    }


}
